# a tooltip support module
# copy & modified from idlelib

from tkinter import *

class ToolTipBase:

    def __init__(self, wdgt):
        self.wdgt = wdgt
        self.tipwindow = None
        self.id = None
        self.x = self.y = 0
        self._id1 = self.wdgt.bind("<Enter>", self.enter, "+")
        self._id2 = self.wdgt.bind("<Leave>", self.leave, "+")
        self._id3 = self.wdgt.bind("<ButtonPress>", self.press, "+")
        self._id4 = self.wdgt.bind("<Motion>", self.move, "+")

    def enter(self, event=None):
        self.schedule()

    def leave(self, event=None):
        self.unschedule()
        self.hidetip()
        
    def press(self,event=None):
        if self.tipwindow:
            self.leave(event)
            
    def move(self, event):
        self.hidetip()
        self.schedule()
        
    def schedule(self):
        self.unschedule()
        self.id = self.wdgt.after(1500, self.showtip)

    def unschedule(self):
        id = self.id
        self.id = None
        if id:
            self.wdgt.after_cancel(id)

    def showtip(self):
        if self.tipwindow:
            return
        x,y=self.wdgt.winfo_pointerxy()
        # The tip window must be offset from the pointer poistion;
        # otherwise when the mouse enters the tip window we get
        # a leave event and it disappears, and then we get an enter
        # event and it reappears, and so on forever :-(
        tw_x = x + 10
        tw_y = y + 10
        self.tipwindow = tw = Toplevel(self.wdgt)
        tw.wm_overrideredirect(1)
        tw.wm_geometry("+%d+%d" % (tw_x, tw_y))
        self.showcontents((x-self.wdgt.winfo_rootx(), y-self.wdgt.winfo_rooty()))
        self.unschedule()

    def showcontents(self, pointer):
        # Override this in derived class
        pass

    def hidetip(self):
        tw = self.tipwindow
        self.tipwindow = None
        if tw:
            tw.destroy()

class ToolTip(ToolTipBase):
    def __init__(self, wdgt, text):
        ToolTipBase.__init__(self, wdgt)
        self.text = text
    def showcontents(self,pointer):
        label = Label(self.tipwindow, text=self.text, justify=LEFT,
                      background="#ffffe0", relief=SOLID, borderwidth=1)
        label.pack()

class ListboxToolTip(ToolTipBase):
    def __init__(self, wdgt, items):
        ToolTipBase.__init__(self, wdgt)
        self.items = items
    def showcontents(self, pointer):
        listbox = Listbox(self.tipwindow, background="#ffffe0")
        listbox.pack()
        for item in self.items:
            listbox.insert(END, item)

class CallbackToolTip(ToolTipBase):
    """
    Using the callback function to show tips.
    The callback function create the widgets in tip window.
    call back function prototype:
    
    show_func(tipwindow, pointer)
    
    return False if do not need show the tip
    """
    def __init__(self, wdgt, show_func):
        ToolTipBase.__init__(self, wdgt)
        self.show_func = show_func
    def showcontents(self, pointer):
        try:
            show=self.show_func(self.tipwindow, pointer)
        except:
            show=False
        if show==False:
            self.hidetip()
        
if __name__ == '__main__':
    # Test code
    root = Tk()
    b1 = Button(root, text="ToolTip", command=root.destroy)
    b1.pack()
    root.update()
    tip1=ToolTip(b1, "Hello world")
    b2 = Button(root, text="ListboxToolTip", command=root.destroy)
    b2.pack()
    root.update()
    tip2 = ListboxToolTip(b2, ["Hello", "world"])
    b3 = Button(root, text="CallbackToolTip", command=root.destroy)
    b3.pack()
    root.update()
    
    class TestTipClass:
        def __init__(self, text):
            self.text=text
        def showtip(self, tipwindow, pointer):     
            listbox = Listbox(tipwindow, background="#ffffe0")
            listbox.pack()
            listbox.insert(END, self.text)
            listbox.insert(END, "pointer: %d,%d"%pointer)
            
    o=TestTipClass("Hello world")
    tip3=CallbackToolTip(b3, o.showtip)     
    root.mainloop()